1 Load packages & Input

library(survival)
library(maxstat)
library(survminer)
library(tidyverse)
library(MultiAssayExperiment)
library(EnvStats)
library(readxl)

source("data/Figure_layouts.R")
load("data/CLL_Proteomics_Setup.RData")
load("data/CLL_Proteomics_ConsensusClustering.RData")
load("data/proteomics_model_noDrug_factors.RData")

colData(multiomics_MAE)$PG <- as.factor(CCP_group5[rownames(colData(multiomics_MAE))])

2 Define functions

This functions extract information from cox model objects

Extract.Cox <- function(x)
        { 
        x <- summary(x)
        p.value<-signif(x$coefficients[1, 5], digits=2)
        beta<-signif(x$coefficients[1, 1], digits=2);#coeficient beta
        HR <-signif(x$coefficients[1, 2], digits=2);#exp(beta)
        wald.test<-signif(x$waldtest["test"], digits=2)
        HR.confint.lower <- signif(x$conf.int[1 ,"lower .95"], 2)
        HR.confint.upper <- signif(x$conf.int[1 ,"upper .95"], 2)
        HR <- paste0(HR, " (", 
                     HR.confint.lower, "-", HR.confint.upper, ")")
        res<-c(beta, HR, wald.test, p.value)
        names(res)<-c("beta", "HR (95% CI for HR)", "wald.test", 
                      "p.value")
        return(res)
        }

This function calculate univeriate/multivariate cox models

Calc.Cox <- function(formula, covariates, dataframe, MultiVars=NULL) {

  if(!is_empty(MultiVars)) {z <- paste(" +", MultiVars, collapse="")}
  else{z <-  NULL}
  
  # Define formulas for univariate model
  cox_formulas <- sapply(covariates, 
                     function(y) as.formula(paste(formula, y, z)))
  
  # Calculate univariate model for all formulas
  cox_models <- lapply(cox_formulas, 
                      function(z){coxph(z, data = dataframe)})
  
  # Extract data from summary objects
  cox_results <- lapply(cox_models, Extract.Cox)
  
  # Transform data to data frame
  results <- t(as.data.frame(cox_results, check.names = FALSE)) %>% 
    as.data.frame() 
  results$p.value <- as.character(results$p.value) %>% as.numeric()
  results$p.value_adj <- p.adjust(results$p.value, method = "BH")

return(results)

}

3 Create lists for results

Results <- list()

4 Prepare data

# Protein
prot <- multiomics_MAE@ExperimentList$proteomics

# Remove all proteins which harbor any NA
prot <- prot[rowSums(is.na(prot))==0, ]

# Remove '-' as it makes problems in formula objects
rownames(prot) <- gsub(rownames(prot), pattern = "-", replacement = "_")

5 Time to next treatment

5.1 Add parameters for cox model

df <- as.data.frame(colData(multiomics_MAE))

# Set 'time' either to observation time (w/o next treatment) or to actual time to next treatment
# Status: 1 for patients w/o next treatment ('censored'), 2 for patients who actually recieved treatment
df <- df %>% 
  dplyr::rename(TTNT = timeDiff_TTNT_orig) %>%
  mutate(time = ifelse(is.na(TTNT), ObsTime, TTNT), 
         status = ifelse(is.na(TTNT), 1, 2))

# Remove without any follow up
df <- filter(df, ObsTime!=0) %>% filter(!treatment_status=="intreatment")

5.2 Univariate analysis without regard to IGHV

5.2.1 Protein

df_short <- filter(df, patient_ID %in% 
                     colnames(multiomics_MAE@ExperimentList$proteomics))
df_short <- dplyr::select(df_short, patient_ID, IGHV, trisomy12, time, status)

# Merge protein data with TTNT within one data frame
prot_surv <- t(prot) %>% as.data.frame() %>% 
  rownames_to_column(var = "patient_ID") %>% 
  right_join(df_short, by="patient_ID")

print(nrow(prot_surv))
## [1] 61
# Apply Calc.Cox
res_prot <- Calc.Cox(formula = 'Surv(time, status)~', 
                     covariates = rownames(prot),
                     dataframe = prot_surv)

# Top 100 proteins (lowest p value)
res_prot %>% rownames_to_column(var = "Symbol") %>% 
  top_n(100, -p.value) %>% arrange(p.value) 
# P value histogram
hist(res_prot$p.value, breaks = 100)

# Save object in list
Results[["Univ.prot_TTNT"]] <- res_prot

message("Number proteins significant (FDR = 5%) associated with TTNT")
sum(res_prot$p.value_adj < 0.05)
## [1] 605
message("Percent proteins significant (FDR = 5%) associated with TTNT")
sum(res_prot$p.value_adj < 0.05) / length(res_prot$p.value_adj)
## [1] 0.0828

5.2.2 Laten factors derived from MOFA

5.2.2.1 Without drug response

# Filter for patients with complete data
df_short <- 
  filter(df, patient_ID %in% rownames(proteomics_model_noDrug_factors)) %>% 
  dplyr::select(patient_ID, time, status, IGHV, trisomy12)

# Merge protein data with TTNT within one data frame
LF_surv <- proteomics_model_noDrug_factors %>% as.data.frame() %>% 
  rownames_to_column(var = "patient_ID") %>% 
  right_join(df_short, by="patient_ID")

print(nrow(LF_surv))
## [1] 72
# Apply Calc.Cox
res_LF <- Calc.Cox(formula = 'Surv(time, status)~', 
                     covariates = colnames(proteomics_model_noDrug_factors),
                     dataframe = LF_surv)

# Top 100 genes (lowest p value)
res_LF %>% 
  rownames_to_column(var = "LF") %>%
  arrange(p.value)
# Save object in list
Results[["Univ.LF_TTNT"]] <- res_LF

6 Kaplan Meier curves

KM.Plot <- function(dataset, name, plot=TRUE) {
  
  # Filter for patients with complete data
  df_short <- df %>%
    filter(patient_ID %in% rownames(dataset)) %>% 
    dplyr::select(patient_ID, time, status)
  
  # Merge protein data with TTNT within one data frame
  df_surv <- dataset %>% 
    as.data.frame() %>% 
    rownames_to_column(var = "patient_ID") %>% 
    right_join(df_short, by="patient_ID") %>% 
    dplyr::select(time, status, name) %>%
    dplyr::rename(Factor = name) %>%
    mutate(BinFactor = Factor)
      
  formel <- as.formula(paste0("Surv(time, status) ~ Factor"))
  
  # Test if dataset is discrete or continuous
  x1 <- dataset %>% as.numeric() %>% unique() %>% na.omit() %>% length()
  x2 <- dataset %>% as.numeric() %>% na.omit() %>% length()
  
  # If dataset is continuous, find optimal Cut-Off
  if(x1/x2 > 0.1) {
    
      mxs.obj <- maxstat.test(Surv(time, status) ~ Factor, data=df_surv,
                              smethod="LogRank", pmethod="exactGauss", 
                              minprop = 0.25, maxprop=0.75, abseps=0.01)
      
      df_surv <- mutate(df_surv, BinFactor=ifelse(Factor > mxs.obj$estimate, "High", "Low"))
      
  }
  
  df_surv$time <- df_surv$time/365
  
  fit <- survfit(Surv(time, status) ~ BinFactor, data = df_surv)
      
  if(plot==TRUE)
    {
      surv_plot <- ggsurvplot(fit, data = df_surv, 
                 pval = TRUE, 
                 pval.size = 4,
                 pval.coord = c(1800, 0.95),
                 xlab = "Time in years",
                 palette = c("#E7B800", "#2E9FDF"),
                 legend.labs = gsub("BinFactor=", "", names(fit$strata) ),
                 ylab="Treatment free survival",
                 legend.title = name,
                 legend = "top",
                 ggtheme = theme_bw())
      print(surv_plot)
      return(surv_plot)
     } else 
       
       { 
         l <- list(fit, df_surv) 
         names(l) <- c("fit", "df")
         return(l)
        }

}

6.1 TTNT

6.1.1 LFs

df <- as.data.frame(colData(multiomics_MAE))

# Set 'time' either to observation time (w/o next treatment) or to actual time to next treatment
# Status: 1 for patients w/o next treatment ('censored'), 2 for patients who actually recieved treatment
df <- df %>% 
  dplyr::rename(TTNT = timeDiff_TTNT_orig) %>%
  mutate(time = ifelse(is.na(TTNT), ObsTime, TTNT), 
         status = ifelse(is.na(TTNT), 1, 2))

# Remove without any follow up
df <- filter(df, ObsTime!=0) %>% filter(!treatment_status=="intreatment")
df_TTNT <-  df

6.2 Correlate hazard ratios of signficantly associated proteins and transcripts

sig_prot_univ <- Results$Univ.prot_TTNT %>% as_tibble()
sig_prot_univ$hgnc_symbol <- rownames(Results$Univ.prot_TTNT)

6.3 Kaplan Meier Curves specific proteins

6.3.1 BCR

df <- df_TTNT
PIK3CD_surv_plot <- KM.Plot(dataset = t(multiomics_MAE@ExperimentList$proteomics), name = "PIK3CD")

PLCG2_surv_plot <- KM.Plot(dataset = t(multiomics_MAE@ExperimentList$proteomics), name = "PLCG2")

6.3.2 Proteins loaded on latent factors

CD20_surv_plot <- KM.Plot(dataset = t(multiomics_MAE@ExperimentList$proteomics), name = "MS4A1")

SAMHD1_surv_plot <- KM.Plot(dataset = t(multiomics_MAE@ExperimentList$proteomics), name = "SAMHD1")

FCRL2_surv_plot <- KM.Plot(dataset = t(multiomics_MAE@ExperimentList$proteomics), name = "FCRL2")

CD40_surv_plot <- KM.Plot(dataset = t(multiomics_MAE@ExperimentList$proteomics), name = "CD40")

6.4 Hazard ratios volcano plot

HR_BCR_volcano <- sig_prot_univ %>% 
  separate(`HR (95% CI for HR)`, into=c("HR_prot", "95% CI for HR"), sep = " \\(") %>%
  mutate(HR_prot=as.numeric(HR_prot), `95% CI for HR`=gsub("\\)", "", `95% CI for HR`) ) %>%
  ggplot(aes(log2(HR_prot), -log10(p.value) )) +
  geom_point(data = . %>% filter(p.value_adj >= 0.05), color="lightgray", alpha=0.5) +
  geom_point(data = . %>% filter(p.value_adj < 0.05), color="#0571b0", alpha=0.5 ) +
  geom_point(data = . %>% filter( hgnc_symbol %in% BCR_genes ), color="orange1" ) +
  pp_sra 
HR_BCR_volcano + ggtitle("Proteins ~ TTNT") + coord_cartesian(ylim=c(0.2,7.3)) +
    ggrepel::geom_label_repel(data = . %>% filter(p.value_adj < 0.05, hgnc_symbol %in% BCR_genes), 
            aes(label= hgnc_symbol), size=3)

6.5 GSEA

sig_prot_univ %>% transmute(hgnc_symbol, rank= base::rank(p.value, ties.method = "first") ) #%>% write.table("/Users/sophierabe/Desktop/PhD/Labor/Proteomics/CLL/GSEA_CLL_Proteomics/191125_TTNT_Protein_GSEAlist.rnk", quote = FALSE, row.names = FALSE, col.names = TRUE, sep = "\t")

7 Figures for Publication

7.1 LFs and Hazard ratios

LF_HR_plot <- 
  Results[["Univ.LF_TTNT"]] %>%
  separate(col = `HR (95% CI for HR)`, into = c("HR", "CI"), sep = "\\(" ) %>%
  mutate(CI = gsub(")", "", CI )) %>%
  separate(CI, into = c("lower_CI", "upper_CI"), sep = "-" ) %>%
  mutate("Hazard ratio" = as.numeric(HR), lower_CI =  as.numeric(lower_CI), upper_CI= as.numeric(upper_CI), LF= rownames(.)) %>%
  mutate("Latent factor" = as.factor(gsub("LF", "", LF ) )) %>%
  mutate("Latent factor" = factor(`Latent factor`, levels = c(1:nrow(Results[["Univ.LF_TTNT"]])) ) ) %>% 
  mutate(sig = if_else( (lower_CI < 1 & upper_CI <1) | (lower_CI > 1 & upper_CI >1), "significant", "NS" )) %>% 
  ggplot(aes( `Latent factor`, `Hazard ratio` )) +
  geom_point(aes(color=sig)) +
  geom_linerange(aes( ymin=lower_CI, ymax= upper_CI, color= sig )) +
  scale_color_manual(values = c("black", "blue")) +
  pp_sra +
  coord_flip(ylim = c(0.22, 4.5)) +
  geom_hline(yintercept = 1, linetype= "dashed") +
  scale_y_log10()

LF_HR_plot +
  geom_text(y= 0.6, aes(label= p.value, color=sig )) +
  theme(legend.position = 'none') 

8 Save important plots

save(  HR_BCR_volcano, PIK3CD_surv_plot,  PLCG2_surv_plot, 
  LF_HR_plot, CD20_surv_plot, SAMHD1_surv_plot, FCRL2_surv_plot, CD40_surv_plot,
file = "RData_plots/CLL_Proteomics_TTNT_Plots.RData")

9 Session Info

sessionInfo()
## R version 4.0.2 (2020-06-22)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Catalina 10.15.7
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] parallel  stats4    stats     graphics  grDevices utils     datasets 
## [8] methods   base     
## 
## other attached packages:
##  [1] DESeq2_1.28.1               readxl_1.3.1               
##  [3] EnvStats_2.3.1              MultiAssayExperiment_1.14.0
##  [5] SummarizedExperiment_1.18.2 DelayedArray_0.14.1        
##  [7] matrixStats_0.56.0          Biobase_2.48.0             
##  [9] GenomicRanges_1.40.0        GenomeInfoDb_1.24.2        
## [11] IRanges_2.22.2              S4Vectors_0.26.1           
## [13] BiocGenerics_0.34.0         forcats_0.5.0              
## [15] stringr_1.4.0               dplyr_1.0.2                
## [17] purrr_0.3.4                 readr_1.3.1                
## [19] tidyr_1.1.2                 tibble_3.0.3               
## [21] tidyverse_1.3.0             survminer_0.4.8            
## [23] ggpubr_0.4.0                ggplot2_3.3.2              
## [25] maxstat_0.7-25              survival_3.2-3             
## [27] knitr_1.29                  BiocStyle_2.16.0           
## 
## loaded via a namespace (and not attached):
##  [1] colorspace_1.4-1       ggsignif_0.6.0         ellipsis_0.3.1        
##  [4] rio_0.5.16             XVector_0.28.0         base64enc_0.1-3       
##  [7] fs_1.5.0               rstudioapi_0.11        farver_2.0.3          
## [10] ggrepel_0.8.2          bit64_4.0.5            AnnotationDbi_1.50.3  
## [13] fansi_0.4.1            mvtnorm_1.1-1          lubridate_1.7.9       
## [16] xml2_1.3.2             splines_4.0.2          geneplotter_1.66.0    
## [19] jsonlite_1.7.1         broom_0.7.0            km.ci_0.5-2           
## [22] annotate_1.66.0        dbplyr_1.4.4           BiocManager_1.30.10   
## [25] compiler_4.0.2         httr_1.4.2             backports_1.1.9       
## [28] assertthat_0.2.1       Matrix_1.2-18          cli_2.0.2             
## [31] htmltools_0.5.0        tools_4.0.2            gtable_0.3.0          
## [34] glue_1.4.2             GenomeInfoDbData_1.2.3 Rcpp_1.0.5            
## [37] carData_3.0-4          cellranger_1.1.0       vctrs_0.3.4           
## [40] exactRankTests_0.8-31  xfun_0.17              openxlsx_4.1.5        
## [43] rvest_0.3.6            lifecycle_0.2.0        rstatix_0.6.0         
## [46] XML_3.99-0.5           zlibbioc_1.34.0        zoo_1.8-8             
## [49] scales_1.1.1           hms_0.5.3              RColorBrewer_1.1-2    
## [52] yaml_2.2.1             curl_4.3               memoise_1.1.0         
## [55] gridExtra_2.3          KMsurv_0.1-5           stringi_1.5.3         
## [58] RSQLite_2.2.0          genefilter_1.70.0      zip_2.1.1             
## [61] BiocParallel_1.22.0    rlang_0.4.7            pkgconfig_2.0.3       
## [64] bitops_1.0-6           evaluate_0.14          lattice_0.20-41       
## [67] labeling_0.3           bit_4.0.4              tidyselect_1.1.0      
## [70] magrittr_1.5           bookdown_0.20          R6_2.4.1              
## [73] magick_2.4.0           generics_0.0.2         DBI_1.1.0             
## [76] pillar_1.4.6           haven_2.3.1            foreign_0.8-80        
## [79] withr_2.2.0            abind_1.4-5            RCurl_1.98-1.2        
## [82] modelr_0.1.8           crayon_1.3.4           car_3.0-9             
## [85] survMisc_0.5.5         rmarkdown_2.3          locfit_1.5-9.4        
## [88] grid_4.0.2             data.table_1.13.0      blob_1.2.1            
## [91] reprex_0.3.0           digest_0.6.25          xtable_1.8-4          
## [94] munsell_0.5.0
LS0tCnRpdGxlOiAiQW5hbHlzaXMgQ0xMIFByb3Rlb21pY3MgLSBDb3ggUmVncmVzc2lvbiBUVFQiCmF1dGhvcjogUm9pZGVyIFRvYmlhcwpkYXRlOiAiYHIgZG9jX2RhdGUoKWAiCm91dHB1dDogCiAgICBCaW9jU3R5bGU6Omh0bWxfZG9jdW1lbnQ6CiAgICAgICAgdG9jOiB0cnVlCiAgICAgICAgc2VsZl9jb250YWluZWQ6IHRydWUKICAgICAgICB0b2NfZmxvYXQ6IHRydWUKICAgICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICAgICAgdG9jX2RlcHRoOiA0CiAgICBCaW9jU3R5bGU6OnBkZl9kb2N1bWVudDoKICAgICAgICB0b2M6IHRydWUKICAgICAgICB0b2NfZGVwdGg6IDQKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCi0tLQoKCmBgYHtyIG9wdGlvbnMsIGluY2x1ZGU9RkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbGlicmFyeShrbml0cikKb3B0aW9ucyhkaWdpdHM9Mywgd2lkdGg9ODApCm9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSx0aWR5PUZBTFNFLGluY2x1ZGU9VFJVRSwKICAgICAgICAgICAgICAgZGV2PWMoJ3BuZycpLCBmaWcuc21hbGw9RkFMU0UsCiAgICAgICAgICAgICAgIGRwaSA9IDMwMCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpCmBgYAoKIyBMb2FkIHBhY2thZ2VzICYgSW5wdXQKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmxpYnJhcnkoc3Vydml2YWwpCmxpYnJhcnkobWF4c3RhdCkKbGlicmFyeShzdXJ2bWluZXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KE11bHRpQXNzYXlFeHBlcmltZW50KQpsaWJyYXJ5KEVudlN0YXRzKQpsaWJyYXJ5KHJlYWR4bCkKCnNvdXJjZSgiZGF0YS9GaWd1cmVfbGF5b3V0cy5SIikKbG9hZCgiZGF0YS9DTExfUHJvdGVvbWljc19TZXR1cC5SRGF0YSIpCmxvYWQoImRhdGEvQ0xMX1Byb3Rlb21pY3NfQ29uc2Vuc3VzQ2x1c3RlcmluZy5SRGF0YSIpCmxvYWQoImRhdGEvcHJvdGVvbWljc19tb2RlbF9ub0RydWdfZmFjdG9ycy5SRGF0YSIpCgpjb2xEYXRhKG11bHRpb21pY3NfTUFFKSRQRyA8LSBhcy5mYWN0b3IoQ0NQX2dyb3VwNVtyb3duYW1lcyhjb2xEYXRhKG11bHRpb21pY3NfTUFFKSldKQpgYGAKCiMgRGVmaW5lIGZ1bmN0aW9ucwpUaGlzIGZ1bmN0aW9ucyBleHRyYWN0IGluZm9ybWF0aW9uIGZyb20gY294IG1vZGVsIG9iamVjdHMKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCkV4dHJhY3QuQ294IDwtIGZ1bmN0aW9uKHgpCiAgICAgICAgeyAKICAgICAgICB4IDwtIHN1bW1hcnkoeCkKICAgICAgICBwLnZhbHVlPC1zaWduaWYoeCRjb2VmZmljaWVudHNbMSwgNV0sIGRpZ2l0cz0yKQogICAgICAgIGJldGE8LXNpZ25pZih4JGNvZWZmaWNpZW50c1sxLCAxXSwgZGlnaXRzPTIpOyNjb2VmaWNpZW50IGJldGEKICAgICAgICBIUiA8LXNpZ25pZih4JGNvZWZmaWNpZW50c1sxLCAyXSwgZGlnaXRzPTIpOyNleHAoYmV0YSkKICAgICAgICB3YWxkLnRlc3Q8LXNpZ25pZih4JHdhbGR0ZXN0WyJ0ZXN0Il0sIGRpZ2l0cz0yKQogICAgICAgIEhSLmNvbmZpbnQubG93ZXIgPC0gc2lnbmlmKHgkY29uZi5pbnRbMSAsImxvd2VyIC45NSJdLCAyKQogICAgICAgIEhSLmNvbmZpbnQudXBwZXIgPC0gc2lnbmlmKHgkY29uZi5pbnRbMSAsInVwcGVyIC45NSJdLCAyKQogICAgICAgIEhSIDwtIHBhc3RlMChIUiwgIiAoIiwgCiAgICAgICAgICAgICAgICAgICAgIEhSLmNvbmZpbnQubG93ZXIsICItIiwgSFIuY29uZmludC51cHBlciwgIikiKQogICAgICAgIHJlczwtYyhiZXRhLCBIUiwgd2FsZC50ZXN0LCBwLnZhbHVlKQogICAgICAgIG5hbWVzKHJlcyk8LWMoImJldGEiLCAiSFIgKDk1JSBDSSBmb3IgSFIpIiwgIndhbGQudGVzdCIsIAogICAgICAgICAgICAgICAgICAgICAgInAudmFsdWUiKQogICAgICAgIHJldHVybihyZXMpCiAgICAgICAgfQoKYGBgCgpUaGlzIGZ1bmN0aW9uIGNhbGN1bGF0ZSB1bml2ZXJpYXRlL211bHRpdmFyaWF0ZSBjb3ggbW9kZWxzCmBgYHtyfQoKQ2FsYy5Db3ggPC0gZnVuY3Rpb24oZm9ybXVsYSwgY292YXJpYXRlcywgZGF0YWZyYW1lLCBNdWx0aVZhcnM9TlVMTCkgewoKICBpZighaXNfZW1wdHkoTXVsdGlWYXJzKSkge3ogPC0gcGFzdGUoIiArIiwgTXVsdGlWYXJzLCBjb2xsYXBzZT0iIil9CiAgZWxzZXt6IDwtICBOVUxMfQogIAogICMgRGVmaW5lIGZvcm11bGFzIGZvciB1bml2YXJpYXRlIG1vZGVsCiAgY294X2Zvcm11bGFzIDwtIHNhcHBseShjb3ZhcmlhdGVzLCAKICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeSkgYXMuZm9ybXVsYShwYXN0ZShmb3JtdWxhLCB5LCB6KSkpCiAgCiAgIyBDYWxjdWxhdGUgdW5pdmFyaWF0ZSBtb2RlbCBmb3IgYWxsIGZvcm11bGFzCiAgY294X21vZGVscyA8LSBsYXBwbHkoY294X2Zvcm11bGFzLCAKICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHope2NveHBoKHosIGRhdGEgPSBkYXRhZnJhbWUpfSkKICAKICAjIEV4dHJhY3QgZGF0YSBmcm9tIHN1bW1hcnkgb2JqZWN0cwogIGNveF9yZXN1bHRzIDwtIGxhcHBseShjb3hfbW9kZWxzLCBFeHRyYWN0LkNveCkKICAKICAjIFRyYW5zZm9ybSBkYXRhIHRvIGRhdGEgZnJhbWUKICByZXN1bHRzIDwtIHQoYXMuZGF0YS5mcmFtZShjb3hfcmVzdWx0cywgY2hlY2submFtZXMgPSBGQUxTRSkpICU+JSAKICAgIGFzLmRhdGEuZnJhbWUoKSAKICByZXN1bHRzJHAudmFsdWUgPC0gYXMuY2hhcmFjdGVyKHJlc3VsdHMkcC52YWx1ZSkgJT4lIGFzLm51bWVyaWMoKQogIHJlc3VsdHMkcC52YWx1ZV9hZGogPC0gcC5hZGp1c3QocmVzdWx0cyRwLnZhbHVlLCBtZXRob2QgPSAiQkgiKQoKcmV0dXJuKHJlc3VsdHMpCgp9CgpgYGAKCgojIENyZWF0ZSBsaXN0cyBmb3IgcmVzdWx0cwpgYGB7cn0KUmVzdWx0cyA8LSBsaXN0KCkKYGBgCgojIFByZXBhcmUgZGF0YQpgYGB7cn0KCiMgUHJvdGVpbgpwcm90IDwtIG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MKCiMgUmVtb3ZlIGFsbCBwcm90ZWlucyB3aGljaCBoYXJib3IgYW55IE5BCnByb3QgPC0gcHJvdFtyb3dTdW1zKGlzLm5hKHByb3QpKT09MCwgXQoKIyBSZW1vdmUgJy0nIGFzIGl0IG1ha2VzIHByb2JsZW1zIGluIGZvcm11bGEgb2JqZWN0cwpyb3duYW1lcyhwcm90KSA8LSBnc3ViKHJvd25hbWVzKHByb3QpLCBwYXR0ZXJuID0gIi0iLCByZXBsYWNlbWVudCA9ICJfIikKYGBgCgojIFRpbWUgdG8gbmV4dCB0cmVhdG1lbnQKIyMgQWRkIHBhcmFtZXRlcnMgZm9yIGNveCBtb2RlbApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkZiA8LSBhcy5kYXRhLmZyYW1lKGNvbERhdGEobXVsdGlvbWljc19NQUUpKQoKIyBTZXQgJ3RpbWUnIGVpdGhlciB0byBvYnNlcnZhdGlvbiB0aW1lICh3L28gbmV4dCB0cmVhdG1lbnQpIG9yIHRvIGFjdHVhbCB0aW1lIHRvIG5leHQgdHJlYXRtZW50CiMgU3RhdHVzOiAxIGZvciBwYXRpZW50cyB3L28gbmV4dCB0cmVhdG1lbnQgKCdjZW5zb3JlZCcpLCAyIGZvciBwYXRpZW50cyB3aG8gYWN0dWFsbHkgcmVjaWV2ZWQgdHJlYXRtZW50CmRmIDwtIGRmICU+JSAKICBkcGx5cjo6cmVuYW1lKFRUTlQgPSB0aW1lRGlmZl9UVE5UX29yaWcpICU+JQogIG11dGF0ZSh0aW1lID0gaWZlbHNlKGlzLm5hKFRUTlQpLCBPYnNUaW1lLCBUVE5UKSwgCiAgICAgICAgIHN0YXR1cyA9IGlmZWxzZShpcy5uYShUVE5UKSwgMSwgMikpCgojIFJlbW92ZSB3aXRob3V0IGFueSBmb2xsb3cgdXAKZGYgPC0gZmlsdGVyKGRmLCBPYnNUaW1lIT0wKSAlPiUgZmlsdGVyKCF0cmVhdG1lbnRfc3RhdHVzPT0iaW50cmVhdG1lbnQiKQpgYGAKCiMjIFVuaXZhcmlhdGUgYW5hbHlzaXMgd2l0aG91dCByZWdhcmQgdG8gSUdIVgojIyMgUHJvdGVpbgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKZGZfc2hvcnQgPC0gZmlsdGVyKGRmLCBwYXRpZW50X0lEICVpbiUgCiAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzKG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MpKQpkZl9zaG9ydCA8LSBkcGx5cjo6c2VsZWN0KGRmX3Nob3J0LCBwYXRpZW50X0lELCBJR0hWLCB0cmlzb215MTIsIHRpbWUsIHN0YXR1cykKCiMgTWVyZ2UgcHJvdGVpbiBkYXRhIHdpdGggVFROVCB3aXRoaW4gb25lIGRhdGEgZnJhbWUKcHJvdF9zdXJ2IDwtIHQocHJvdCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJwYXRpZW50X0lEIikgJT4lIAogIHJpZ2h0X2pvaW4oZGZfc2hvcnQsIGJ5PSJwYXRpZW50X0lEIikKCnByaW50KG5yb3cocHJvdF9zdXJ2KSkKCiMgQXBwbHkgQ2FsYy5Db3gKcmVzX3Byb3QgPC0gQ2FsYy5Db3goZm9ybXVsYSA9ICdTdXJ2KHRpbWUsIHN0YXR1cyl+JywgCiAgICAgICAgICAgICAgICAgICAgIGNvdmFyaWF0ZXMgPSByb3duYW1lcyhwcm90KSwKICAgICAgICAgICAgICAgICAgICAgZGF0YWZyYW1lID0gcHJvdF9zdXJ2KQoKIyBUb3AgMTAwIHByb3RlaW5zIChsb3dlc3QgcCB2YWx1ZSkKcmVzX3Byb3QgJT4lIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiU3ltYm9sIikgJT4lIAogIHRvcF9uKDEwMCwgLXAudmFsdWUpICU+JSBhcnJhbmdlKHAudmFsdWUpIAoKIyBQIHZhbHVlIGhpc3RvZ3JhbQpoaXN0KHJlc19wcm90JHAudmFsdWUsIGJyZWFrcyA9IDEwMCkKCiMgU2F2ZSBvYmplY3QgaW4gbGlzdApSZXN1bHRzW1siVW5pdi5wcm90X1RUTlQiXV0gPC0gcmVzX3Byb3QKCm1lc3NhZ2UoIk51bWJlciBwcm90ZWlucyBzaWduaWZpY2FudCAoRkRSID0gNSUpIGFzc29jaWF0ZWQgd2l0aCBUVE5UIikKc3VtKHJlc19wcm90JHAudmFsdWVfYWRqIDwgMC4wNSkKbWVzc2FnZSgiUGVyY2VudCBwcm90ZWlucyBzaWduaWZpY2FudCAoRkRSID0gNSUpIGFzc29jaWF0ZWQgd2l0aCBUVE5UIikKc3VtKHJlc19wcm90JHAudmFsdWVfYWRqIDwgMC4wNSkgLyBsZW5ndGgocmVzX3Byb3QkcC52YWx1ZV9hZGopCmBgYAoKIyMjIExhdGVuIGZhY3RvcnMgZGVyaXZlZCBmcm9tIE1PRkEKIyMjIyBXaXRob3V0IGRydWcgcmVzcG9uc2UKYGBge3J9CiMgRmlsdGVyIGZvciBwYXRpZW50cyB3aXRoIGNvbXBsZXRlIGRhdGEKZGZfc2hvcnQgPC0gCiAgZmlsdGVyKGRmLCBwYXRpZW50X0lEICVpbiUgcm93bmFtZXMocHJvdGVvbWljc19tb2RlbF9ub0RydWdfZmFjdG9ycykpICU+JSAKICBkcGx5cjo6c2VsZWN0KHBhdGllbnRfSUQsIHRpbWUsIHN0YXR1cywgSUdIViwgdHJpc29teTEyKQoKIyBNZXJnZSBwcm90ZWluIGRhdGEgd2l0aCBUVE5UIHdpdGhpbiBvbmUgZGF0YSBmcmFtZQpMRl9zdXJ2IDwtIHByb3Rlb21pY3NfbW9kZWxfbm9EcnVnX2ZhY3RvcnMgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJwYXRpZW50X0lEIikgJT4lIAogIHJpZ2h0X2pvaW4oZGZfc2hvcnQsIGJ5PSJwYXRpZW50X0lEIikKCnByaW50KG5yb3coTEZfc3VydikpCgojIEFwcGx5IENhbGMuQ294CnJlc19MRiA8LSBDYWxjLkNveChmb3JtdWxhID0gJ1N1cnYodGltZSwgc3RhdHVzKX4nLCAKICAgICAgICAgICAgICAgICAgICAgY292YXJpYXRlcyA9IGNvbG5hbWVzKHByb3Rlb21pY3NfbW9kZWxfbm9EcnVnX2ZhY3RvcnMpLAogICAgICAgICAgICAgICAgICAgICBkYXRhZnJhbWUgPSBMRl9zdXJ2KQoKIyBUb3AgMTAwIGdlbmVzIChsb3dlc3QgcCB2YWx1ZSkKcmVzX0xGICU+JSAKICByb3duYW1lc190b19jb2x1bW4odmFyID0gIkxGIikgJT4lCiAgYXJyYW5nZShwLnZhbHVlKQoKIyBTYXZlIG9iamVjdCBpbiBsaXN0ClJlc3VsdHNbWyJVbml2LkxGX1RUTlQiXV0gPC0gcmVzX0xGCgpgYGAKCiMgS2FwbGFuIE1laWVyIGN1cnZlcwpgYGB7cn0KS00uUGxvdCA8LSBmdW5jdGlvbihkYXRhc2V0LCBuYW1lLCBwbG90PVRSVUUpIHsKICAKICAjIEZpbHRlciBmb3IgcGF0aWVudHMgd2l0aCBjb21wbGV0ZSBkYXRhCiAgZGZfc2hvcnQgPC0gZGYgJT4lCiAgICBmaWx0ZXIocGF0aWVudF9JRCAlaW4lIHJvd25hbWVzKGRhdGFzZXQpKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KHBhdGllbnRfSUQsIHRpbWUsIHN0YXR1cykKICAKICAjIE1lcmdlIHByb3RlaW4gZGF0YSB3aXRoIFRUTlQgd2l0aGluIG9uZSBkYXRhIGZyYW1lCiAgZGZfc3VydiA8LSBkYXRhc2V0ICU+JSAKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgICByb3duYW1lc190b19jb2x1bW4odmFyID0gInBhdGllbnRfSUQiKSAlPiUgCiAgICByaWdodF9qb2luKGRmX3Nob3J0LCBieT0icGF0aWVudF9JRCIpICU+JSAKICAgIGRwbHlyOjpzZWxlY3QodGltZSwgc3RhdHVzLCBuYW1lKSAlPiUKICAgIGRwbHlyOjpyZW5hbWUoRmFjdG9yID0gbmFtZSkgJT4lCiAgICBtdXRhdGUoQmluRmFjdG9yID0gRmFjdG9yKQogICAgICAKICBmb3JtZWwgPC0gYXMuZm9ybXVsYShwYXN0ZTAoIlN1cnYodGltZSwgc3RhdHVzKSB+IEZhY3RvciIpKQogIAogICMgVGVzdCBpZiBkYXRhc2V0IGlzIGRpc2NyZXRlIG9yIGNvbnRpbnVvdXMKICB4MSA8LSBkYXRhc2V0ICU+JSBhcy5udW1lcmljKCkgJT4lIHVuaXF1ZSgpICU+JSBuYS5vbWl0KCkgJT4lIGxlbmd0aCgpCiAgeDIgPC0gZGF0YXNldCAlPiUgYXMubnVtZXJpYygpICU+JSBuYS5vbWl0KCkgJT4lIGxlbmd0aCgpCiAgCiAgIyBJZiBkYXRhc2V0IGlzIGNvbnRpbnVvdXMsIGZpbmQgb3B0aW1hbCBDdXQtT2ZmCiAgaWYoeDEveDIgPiAwLjEpIHsKICAgIAogICAgICBteHMub2JqIDwtIG1heHN0YXQudGVzdChTdXJ2KHRpbWUsIHN0YXR1cykgfiBGYWN0b3IsIGRhdGE9ZGZfc3VydiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc21ldGhvZD0iTG9nUmFuayIsIHBtZXRob2Q9ImV4YWN0R2F1c3MiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWlucHJvcCA9IDAuMjUsIG1heHByb3A9MC43NSwgYWJzZXBzPTAuMDEpCiAgICAgIAogICAgICBkZl9zdXJ2IDwtIG11dGF0ZShkZl9zdXJ2LCBCaW5GYWN0b3I9aWZlbHNlKEZhY3RvciA+IG14cy5vYmokZXN0aW1hdGUsICJIaWdoIiwgIkxvdyIpKQogICAgICAKICB9CiAgCiAgZGZfc3VydiR0aW1lIDwtIGRmX3N1cnYkdGltZS8zNjUKICAKICBmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIHN0YXR1cykgfiBCaW5GYWN0b3IsIGRhdGEgPSBkZl9zdXJ2KQogICAgICAKICBpZihwbG90PT1UUlVFKQogICAgewogICAgICBzdXJ2X3Bsb3QgPC0gZ2dzdXJ2cGxvdChmaXQsIGRhdGEgPSBkZl9zdXJ2LCAKICAgICAgICAgICAgICAgICBwdmFsID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgcHZhbC5zaXplID0gNCwKICAgICAgICAgICAgICAgICBwdmFsLmNvb3JkID0gYygxODAwLCAwLjk1KSwKICAgICAgICAgICAgICAgICB4bGFiID0gIlRpbWUgaW4geWVhcnMiLAogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBjKCIjRTdCODAwIiwgIiMyRTlGREYiKSwKICAgICAgICAgICAgICAgICBsZWdlbmQubGFicyA9IGdzdWIoIkJpbkZhY3Rvcj0iLCAiIiwgbmFtZXMoZml0JHN0cmF0YSkgKSwKICAgICAgICAgICAgICAgICB5bGFiPSJUcmVhdG1lbnQgZnJlZSBzdXJ2aXZhbCIsCiAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gbmFtZSwKICAgICAgICAgICAgICAgICBsZWdlbmQgPSAidG9wIiwKICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfYncoKSkKICAgICAgcHJpbnQoc3Vydl9wbG90KQogICAgICByZXR1cm4oc3Vydl9wbG90KQogICAgIH0gZWxzZSAKICAgICAgIAogICAgICAgeyAKICAgICAgICAgbCA8LSBsaXN0KGZpdCwgZGZfc3VydikgCiAgICAgICAgIG5hbWVzKGwpIDwtIGMoImZpdCIsICJkZiIpCiAgICAgICAgIHJldHVybihsKQogICAgICAgIH0KCn0KCmBgYAoKCiMjIFRUTlQKIyMjIExGcwpgYGB7ciBmaWcuc21hbGw9VH0KZGYgPC0gYXMuZGF0YS5mcmFtZShjb2xEYXRhKG11bHRpb21pY3NfTUFFKSkKCiMgU2V0ICd0aW1lJyBlaXRoZXIgdG8gb2JzZXJ2YXRpb24gdGltZSAody9vIG5leHQgdHJlYXRtZW50KSBvciB0byBhY3R1YWwgdGltZSB0byBuZXh0IHRyZWF0bWVudAojIFN0YXR1czogMSBmb3IgcGF0aWVudHMgdy9vIG5leHQgdHJlYXRtZW50ICgnY2Vuc29yZWQnKSwgMiBmb3IgcGF0aWVudHMgd2hvIGFjdHVhbGx5IHJlY2lldmVkIHRyZWF0bWVudApkZiA8LSBkZiAlPiUgCiAgZHBseXI6OnJlbmFtZShUVE5UID0gdGltZURpZmZfVFROVF9vcmlnKSAlPiUKICBtdXRhdGUodGltZSA9IGlmZWxzZShpcy5uYShUVE5UKSwgT2JzVGltZSwgVFROVCksIAogICAgICAgICBzdGF0dXMgPSBpZmVsc2UoaXMubmEoVFROVCksIDEsIDIpKQoKIyBSZW1vdmUgd2l0aG91dCBhbnkgZm9sbG93IHVwCmRmIDwtIGZpbHRlcihkZiwgT2JzVGltZSE9MCkgJT4lIGZpbHRlcighdHJlYXRtZW50X3N0YXR1cz09ImludHJlYXRtZW50IikKZGZfVFROVCA8LSAgZGYKCmBgYAoKIyMgQ29ycmVsYXRlIGhhemFyZCByYXRpb3Mgb2Ygc2lnbmZpY2FudGx5IGFzc29jaWF0ZWQgcHJvdGVpbnMgYW5kIHRyYW5zY3JpcHRzCmBgYHtyIGNvcnJlbGF0ZSBoYXphcmQgcmF0aW9zfQpzaWdfcHJvdF91bml2IDwtIFJlc3VsdHMkVW5pdi5wcm90X1RUTlQgJT4lIGFzX3RpYmJsZSgpCnNpZ19wcm90X3VuaXYkaGduY19zeW1ib2wgPC0gcm93bmFtZXMoUmVzdWx0cyRVbml2LnByb3RfVFROVCkKYGBgCgojIyBLYXBsYW4gTWVpZXIgQ3VydmVzIHNwZWNpZmljIHByb3RlaW5zCiMjIyBCQ1IKYGBge3J9CmRmIDwtIGRmX1RUTlQKUElLM0NEX3N1cnZfcGxvdCA8LSBLTS5QbG90KGRhdGFzZXQgPSB0KG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MpLCBuYW1lID0gIlBJSzNDRCIpClBMQ0cyX3N1cnZfcGxvdCA8LSBLTS5QbG90KGRhdGFzZXQgPSB0KG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MpLCBuYW1lID0gIlBMQ0cyIikKYGBgCgojIyMgUHJvdGVpbnMgbG9hZGVkIG9uIGxhdGVudCBmYWN0b3JzCmBgYHtyfQpDRDIwX3N1cnZfcGxvdCA8LSBLTS5QbG90KGRhdGFzZXQgPSB0KG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MpLCBuYW1lID0gIk1TNEExIikKU0FNSEQxX3N1cnZfcGxvdCA8LSBLTS5QbG90KGRhdGFzZXQgPSB0KG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MpLCBuYW1lID0gIlNBTUhEMSIpCkZDUkwyX3N1cnZfcGxvdCA8LSBLTS5QbG90KGRhdGFzZXQgPSB0KG11bHRpb21pY3NfTUFFQEV4cGVyaW1lbnRMaXN0JHByb3Rlb21pY3MpLCBuYW1lID0gIkZDUkwyIikKQ0Q0MF9zdXJ2X3Bsb3QgPC0gS00uUGxvdChkYXRhc2V0ID0gdChtdWx0aW9taWNzX01BRUBFeHBlcmltZW50TGlzdCRwcm90ZW9taWNzKSwgbmFtZSA9ICJDRDQwIikKYGBgCgojIyBIYXphcmQgcmF0aW9zIHZvbGNhbm8gcGxvdApgYGB7ciBoYXphcmQgcmF0aW9zLCBkZXBlbmRzb249ImNvcnJlbGF0ZSBoYXphcmQgcmF0aW9zIn0KSFJfQkNSX3ZvbGNhbm8gPC0gc2lnX3Byb3RfdW5pdiAlPiUgCiAgc2VwYXJhdGUoYEhSICg5NSUgQ0kgZm9yIEhSKWAsIGludG89YygiSFJfcHJvdCIsICI5NSUgQ0kgZm9yIEhSIiksIHNlcCA9ICIgXFwoIikgJT4lCiAgbXV0YXRlKEhSX3Byb3Q9YXMubnVtZXJpYyhIUl9wcm90KSwgYDk1JSBDSSBmb3IgSFJgPWdzdWIoIlxcKSIsICIiLCBgOTUlIENJIGZvciBIUmApICkgJT4lCiAgZ2dwbG90KGFlcyhsb2cyKEhSX3Byb3QpLCAtbG9nMTAocC52YWx1ZSkgKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IC4gJT4lIGZpbHRlcihwLnZhbHVlX2FkaiA+PSAwLjA1KSwgY29sb3I9ImxpZ2h0Z3JheSIsIGFscGhhPTAuNSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IC4gJT4lIGZpbHRlcihwLnZhbHVlX2FkaiA8IDAuMDUpLCBjb2xvcj0iIzA1NzFiMCIsIGFscGhhPTAuNSApICsKICBnZW9tX3BvaW50KGRhdGEgPSAuICU+JSBmaWx0ZXIoIGhnbmNfc3ltYm9sICVpbiUgQkNSX2dlbmVzICksIGNvbG9yPSJvcmFuZ2UxIiApICsKICBwcF9zcmEgCkhSX0JDUl92b2xjYW5vICsgZ2d0aXRsZSgiUHJvdGVpbnMgfiBUVE5UIikgKyBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAuMiw3LjMpKSArCiAgICBnZ3JlcGVsOjpnZW9tX2xhYmVsX3JlcGVsKGRhdGEgPSAuICU+JSBmaWx0ZXIocC52YWx1ZV9hZGogPCAwLjA1LCBoZ25jX3N5bWJvbCAlaW4lIEJDUl9nZW5lcyksIAogICAgICAgICAgICBhZXMobGFiZWw9IGhnbmNfc3ltYm9sKSwgc2l6ZT0zKQpgYGAKCiMjIEdTRUEKYGBge3J9CnNpZ19wcm90X3VuaXYgJT4lIHRyYW5zbXV0ZShoZ25jX3N5bWJvbCwgcmFuaz0gYmFzZTo6cmFuayhwLnZhbHVlLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpICkgIyU+JSB3cml0ZS50YWJsZSgiL1VzZXJzL3NvcGhpZXJhYmUvRGVza3RvcC9QaEQvTGFib3IvUHJvdGVvbWljcy9DTEwvR1NFQV9DTExfUHJvdGVvbWljcy8xOTExMjVfVFROVF9Qcm90ZWluX0dTRUFsaXN0LnJuayIsIHF1b3RlID0gRkFMU0UsIHJvdy5uYW1lcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBUUlVFLCBzZXAgPSAiXHQiKQpgYGAKCiMgRmlndXJlcyBmb3IgUHVibGljYXRpb24KIyMgTEZzIGFuZCBIYXphcmQgcmF0aW9zCmBgYHtyfQpMRl9IUl9wbG90IDwtIAogIFJlc3VsdHNbWyJVbml2LkxGX1RUTlQiXV0gJT4lCiAgc2VwYXJhdGUoY29sID0gYEhSICg5NSUgQ0kgZm9yIEhSKWAsIGludG8gPSBjKCJIUiIsICJDSSIpLCBzZXAgPSAiXFwoIiApICU+JQogIG11dGF0ZShDSSA9IGdzdWIoIikiLCAiIiwgQ0kgKSkgJT4lCiAgc2VwYXJhdGUoQ0ksIGludG8gPSBjKCJsb3dlcl9DSSIsICJ1cHBlcl9DSSIpLCBzZXAgPSAiLSIgKSAlPiUKICBtdXRhdGUoIkhhemFyZCByYXRpbyIgPSBhcy5udW1lcmljKEhSKSwgbG93ZXJfQ0kgPSAgYXMubnVtZXJpYyhsb3dlcl9DSSksIHVwcGVyX0NJPSBhcy5udW1lcmljKHVwcGVyX0NJKSwgTEY9IHJvd25hbWVzKC4pKSAlPiUKICBtdXRhdGUoIkxhdGVudCBmYWN0b3IiID0gYXMuZmFjdG9yKGdzdWIoIkxGIiwgIiIsIExGICkgKSkgJT4lCiAgbXV0YXRlKCJMYXRlbnQgZmFjdG9yIiA9IGZhY3RvcihgTGF0ZW50IGZhY3RvcmAsIGxldmVscyA9IGMoMTpucm93KFJlc3VsdHNbWyJVbml2LkxGX1RUTlQiXV0pKSApICkgJT4lIAogIG11dGF0ZShzaWcgPSBpZl9lbHNlKCAobG93ZXJfQ0kgPCAxICYgdXBwZXJfQ0kgPDEpIHwgKGxvd2VyX0NJID4gMSAmIHVwcGVyX0NJID4xKSwgInNpZ25pZmljYW50IiwgIk5TIiApKSAlPiUgCiAgZ2dwbG90KGFlcyggYExhdGVudCBmYWN0b3JgLCBgSGF6YXJkIHJhdGlvYCApKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9c2lnKSkgKwogIGdlb21fbGluZXJhbmdlKGFlcyggeW1pbj1sb3dlcl9DSSwgeW1heD0gdXBwZXJfQ0ksIGNvbG9yPSBzaWcgKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsICJibHVlIikpICsKICBwcF9zcmEgKwogIGNvb3JkX2ZsaXAoeWxpbSA9IGMoMC4yMiwgNC41KSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSAiZGFzaGVkIikgKwogIHNjYWxlX3lfbG9nMTAoKQoKTEZfSFJfcGxvdCArCiAgZ2VvbV90ZXh0KHk9IDAuNiwgYWVzKGxhYmVsPSBwLnZhbHVlLCBjb2xvcj1zaWcgKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgCmBgYAoKIyBTYXZlIGltcG9ydGFudCBwbG90cwpgYGB7ciBzYXZlIGltYWdlfQpzYXZlKCAgSFJfQkNSX3ZvbGNhbm8sIFBJSzNDRF9zdXJ2X3Bsb3QsICBQTENHMl9zdXJ2X3Bsb3QsIAogIExGX0hSX3Bsb3QsIENEMjBfc3Vydl9wbG90LCBTQU1IRDFfc3Vydl9wbG90LCBGQ1JMMl9zdXJ2X3Bsb3QsIENENDBfc3Vydl9wbG90LApmaWxlID0gIlJEYXRhX3Bsb3RzL0NMTF9Qcm90ZW9taWNzX1RUTlRfUGxvdHMuUkRhdGEiKQpgYGAKCiMgU2Vzc2lvbiBJbmZvCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNlc3Npb25JbmZvKCkKYGBgCgo=